#include "os_timers.h"
#include "user_timers.h"
#include "timers.h"
#include "loader.h"
#include "queue.h"
#include "scheduler.h"
#include "os_io.h"

static bool timers_initialized = FALSE;
static uint32_t timer_entry_size = 0;


void cancel_timer(uint32_t data)
{
    user_timer_data_t *timer_data = (user_timer_data_t *)data;
    err_t err_code = ERR_SUCCESS;

    if (!timer_data) { 
        return;
    }

    if (!timer_data->platform_handle) {
        timer_data->timer_err = ERR_INVALID;
	return;
    }

    err_code = ERR_SUCCESS;

    if (timer_data->state == TIMER_EXPIRED) {
        // Timer already expired, and is in the queue of the process
	timer_data->state = TIMER_CANCELLED;
    } else if (timer_data->state != TIMER_RUNNING)  {
        err_code = ERR_INVALID_STATE;
    } else {
        err_code = cancel_submitted_timer(timer_data->platform_handle);
    }

    if (err_code == ERR_SUCCESS) {
        free((void *)timer_data->platform_handle);
	timer_data->platform_handle = NULL;
    }

    timer_data->timer_err = err_code;
    return;
}

/*
* User request through SW interrupt
* to start a timer
*/
void start_timer(uint32_t data)
{
    err_t tim_err = ERR_SUCCESS;
    user_timer_data_t *timer_data = (user_timer_data_t *)data;
    //os_timer_ctx_t *timer_ctx = NULL;
    void *tim_entry = NULL;
    err_t tim_error;
    queue_t *q_handle;
    uint32_t pid;


    /* Validations */
    if (!timer_data) { return; }

    if (!timers_initialized) { tim_err = ERR_NOT_INITIALIZED; }
    if (timer_data->interval < MIN_TIMER_INTERVAL) { tim_err = ERR_INVALID; }
    if (!timer_data->user_handler) { tim_err = ERR_INVALID; }
    timer_data->timer_err = tim_err;

    if (tim_err != ERR_SUCCESS) { return; }

    pid = get_current_pid();
    q_handle = qid_to_q_handle(timer_data->q_id, pid);
    if (q_handle == NULL) {
        // Invalid queue_id
	tim_err = ERR_INVALID;
    }

    //printf("OS_TIMERS: Allocating timer entry\n");
    tim_entry = (void *)calloc(1, timer_entry_size); /* Kernel memory */
    if (!tim_entry) {
        printf("Could not allocate timer entry of size: 0x%x\n", timer_entry_size);
        timer_data->timer_err = ERR_NO_MEM;
        return;
    }

    timer_data->state = TIMER_RUNNING;
    timer_data->pid = pid;
    timer_data->q_handle = q_handle;

    tim_error = timer_submit(tim_entry, \
         (void *)timer_data, timer_data->interval);

    if (tim_error == ERR_SUCCESS) {
        //printf("OS_TIMERS: timer submitted successfully\n");
        timer_data->timer_err = ERR_SUCCESS;
	timer_data->platform_handle = (void *)tim_entry;
	//printf("%s: timer submitted: 0x%x\n", __FILE__, data);
    } else {
        printf("OS_TIMERS: Error in submitting timer: 0x%x\n", tim_error);
	free(tim_entry);
	timer_data->timer_err = ERR_INTERNAL_ERROR;
    }

    return;

}


void timer_expired(void *tim_entry, void *timer_data)
{
    user_timer_data_t *ctx = (user_timer_data_t *)timer_data;
    pcb_t *receiver;

    if (!ctx) { return; }

    if (ctx->state == TIMER_CANCELLED) {
	free((uint8_t *)ctx);        // free to kernel memory
	free((uint8_t *)tim_entry);
	return;
    }

    ctx->state = TIMER_EXPIRED;


#ifndef NO_VM
    load_page_table(ctx->pid);
#endif

    // Post message to application if timer has expired
    ctx->msg_id = Q_MSG_TYPE_TIMER;
    ctx->q_handle->tail->next = (queue_msg_t *)ctx;
    ctx->q_handle->tail = (queue_msg_t *)ctx;

    receiver = get_process(ctx->pid);
    if (receiver->process_state == process_waiting && \
        receiver->blocked_res == ctx->q_handle->block)
    {
       // printf("Receiver (0x%x) unblocked and moved to ready queue\n", (uint32_t)receiver);
        add_process_to_ready_queue(receiver);
    }

    free(tim_entry);

    //printf("%s: Timer expired: 0x%x\n", __FILE__, (uint32_t)ctx);

    return;
}



void initialize_timers(void)
{
    err_t tim_err;
    uint32_t tim_entry_size = 0;

    tim_err = timer_init(&timer_expired, &tim_entry_size);
    if (tim_err == ERR_SUCCESS) {
        timer_entry_size = tim_entry_size;
	timers_initialized = TRUE;
    } else {
        printf("Failed to initialize timers. err_code:0x%x\n", tim_err);
    }

    return;
}
